package routower

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/nats-io/nats.go"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"strings"
	"sync"
	"time"
	"wzgames/internal/constant"
	"wzgames/internal/gdao/roudao"
	"wzgames/internal/gloader"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/roulettep"
	"wzgames/internal/response"
	"wzgames/internal/secure"
	"wzgames/internal/secure/routil"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/components/mysql"
	"wzgames/pkg/components/redis"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/stringutil"
)

// 新轮盘播报

type RouManager struct {
	mu      sync.RWMutex
	pid     *actor.PID
	inst    *gamep.GameInst           // 新轮盘游戏实例
	cfg     *roulettep.RouletteConfig // 游戏实例配置
	ab      *RouBetMgr                // 新轮盘投注统计
	rouHelp *routil.RouUtil           // 新轮盘 投注 派彩 结果计算

	betSub *nats.Subscription // 监听投注情况

	instIdStr string
	dao       *roudao.RouletteDao
	ds        *mysql.DS
	redisC    *redis.RedisClient

	flews     *roulettep.RouFlews // 历史轮次信息
	runEntity *roulettep.RouletteRoundEntity
	runRound  *roulettep.RouRound // 当前轮次

	cdRunner *CdRunner // 轮次倒计时 播报

	keyCountDown string // nats key，倒计时播报
	roundDir     string
	stateDir     string

	onlines int64

	master bool
	status mcomm.RunState
}

func NewRouManager(inst *gamep.GameInst) *RouManager {
	s := strconv.FormatInt(inst.InstID, 10)
	roundDir := fmt.Sprintf("%s/round-%v", constant.CcRoulette, inst.InstID)
	am := &RouManager{
		inst:         inst,
		instIdStr:    s,
		flews:        &roulettep.RouFlews{},
		keyCountDown: constant.RouCountDownNKey + "." + s,
		roundDir:     roundDir,
		stateDir:     fmt.Sprintf("%s/state", roundDir),
		cdRunner:     &CdRunner{},
		rouHelp:      &routil.RouUtil{},
		status:       mcomm.RunState_RsOnIdle,
	}
	e := am.updateConf()
	if e != nil {
		return nil
	}

	name := fmt.Sprintf("roulette-mgr-%s", strconv.FormatInt(inst.InstID, 10))
	props := actor.PropsFromProducer(func() actor.Actor { return am })
	pid, err := actorservice.ActorSystem().Root.SpawnNamed(props, name)
	if err != nil {
		logger.Error("[RouTower] spawn roulette manager actor error", zap.Int64("InstId", inst.InstID), zap.Error(err))
		return nil
	}
	am.pid = pid
	am.ab = newRouBetMgr(inst.InstID)
	am.cdRunner.Init(am) // 初始化 倒计时播报
	return am
}

func (am *RouManager) Pid() *actor.PID {
	return am.pid
}

// updateConf 新轮盘配置动态更新
func (am *RouManager) updateConf() error {
	am.mu.Lock()
	defer am.mu.Unlock()
	if am.cfg == nil {
		am.cfg = &roulettep.RouletteConfig{}
	}
	err := proto.Unmarshal(am.inst.Conf, am.cfg)
	if err != nil {
		logger.Error("[RouTower] update Conf on proto.Unmarshal", zap.Int64("InstId", am.inst.InstID), zap.Error(err))
		return err
	}
	return nil
}

func (am *RouManager) Receive(ctx actor.Context) {
	switch m := ctx.Message().(type) {
	case StartRoulette: // 启动
		am.master = true
		am.startRoulette()
	case NewRound: // 新的轮次
		err := am.NewRound(m.RoundId)
		if err == nil {
			// 允许投注
			am.ab.Clean()
			am.changeToState(constant.RouBetState)
		} else {
			TimerAfter(time.Second*3, am.NextRound, nil)
		}
	case *roulettep.RouRound:
		am.updateFlews(m) // 轮次结束后更新历史轮次列表
	default:
	}
}

// Update 新轮盘实例动态更新
func (am *RouManager) Update(inst *gamep.GameInst) {
	am.mu.Lock()
	oldStat := am.inst.Status
	am.inst = inst
	am.mu.Unlock()
	_ = am.updateConf()
	if !am.master {
		return
	}
	// 状态变更可以启动或停止播报
	if oldStat != mcomm.Status_Up && inst.Status == mcomm.Status_Up {
		am.startRoulette()
	} else if oldStat == mcomm.Status_Up && inst.Status != mcomm.Status_Up {
		am.stop()
	}
}

func (am *RouManager) Config() *roulettep.RouletteConfig {
	return am.cfg
}

func (am *RouManager) startRoulette() {
	defer logger.Recover()

	if am.inst.Status != mcomm.Status_Up || am.status == mcomm.RunState_RsOnRun {
		return
	}
	if am.status == mcomm.RunState_RsOnStop { // 停止后重新启动
		am.status = mcomm.RunState_RsOnRun
		logger.Info("[RouTower] roulette restart", zap.String("InstId", am.instIdStr))
		TimerAfter(time.Second*5, am.SettleNext, nil)
		return
	}
	am.status = mcomm.RunState_RsOnRun
	am.redisC = gloader.CommMgr().RedisPool()
	am.ds = gloader.CommMgr().GetDBProxy(constant.MysqlOfRoulette)
	am.dao = &roudao.RouletteDao{Driver: am.ds.Ds()}
	// 监听投注等
	sub2, err1 := gloader.CommMgr().NatsC().Subscribe(constant.RouBetNKeySub+"."+am.instIdStr, am.handleBetInfo)
	if err1 != nil {
		logger.Error("[RouTower] start roulette manager fail on subscribe", zap.Int64("InstID", am.inst.InstID), zap.String("SubKey", constant.AvtBetNKeySub), zap.Error(err1))
		return
	}
	am.betSub = sub2
	err := am.initRounds() // 初始化轮次，并开启新一轮
	if err != nil {
		logger.Error("fail to init !")
		return
	}

	am.updateOnlineToCache(nil)
	logger.Info("[RouTower] roulette big start", zap.String("InstId", am.instIdStr))
}

func (am *RouManager) handleBetInfo(msg *nats.Msg) {
	if am.runRound == nil {
		return
	}
	betNode := response.AcquireBetNote()
	err := proto.Unmarshal(msg.Data, betNode)
	if err != nil {
		logger.Error("[RouTower] process bet fail on proto.Unmarshal", zap.Int64("InstID", am.inst.InstID), zap.Error(err))
		response.ReleaseBetNote(betNode)
		return
	}
	actorservice.Send(am.ab.Pid(), betNode)
}

func (am *RouManager) stop() {
	am.mu.Lock()
	am.status = mcomm.RunState_RsOnStop
	am.mu.Unlock()
	logger.Info("[RouTower] roulette big stop", zap.String("InstId", am.instIdStr))
}

func (am *RouManager) genServerSeed() string {
	return stringutil.RandomString(40)
}

func (am *RouManager) initRounds() error {
	// 取最近10个轮次
	topRounds, err := am.dao.GetTopRounds(am.inst.InstID)
	if err != nil {
		logger.Error("[RouTower] init rounds fail", zap.Int64("InstID", am.inst.InstID), zap.Error(err))
		return err
	}
	var roundId int64
	if len(topRounds) == 0 {
		roundId = 0
	} else {
		roundId = topRounds[0].Rid
	}

	var flews []*roulettep.RouRound
	for _, rd := range topRounds {
		if rd.Status != constant.RouOpenState {
			continue
		}
		first3 := &roulettep.First3Bets{}
		if rd.Clientseeds != nil {
			err = json.Unmarshal(rd.Clientseeds, first3)
			if err != nil {
				logger.Error("[RouTower] init flews fail on Unmarshal", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", rd.Rid), zap.Error(err))
				continue
			}
		}
		flews = append(flews, &roulettep.RouRound{
			RoundId:  rd.Rid,
			Start:    rd.Start,
			End:      rd.End,
			Seed:     rd.Sseed,
			First3:   first3,
			H512:     rd.H512,
			RetNum:   rd.RetNum,
			RetColor: rd.RetColor,
			RetGrp:   rd.RetGrp,
			Seed256:  rd.Sseed256,
		})
	}
	am.initFlews(flews) // 初始化历史轮次到Cache
	// 开启下一轮次
	actorservice.Send(am.pid, NewRound{RoundId: roundId})
	return nil
}

// checkRoundId 轮次ID格式化、递增、判断跨天情况
func (am *RouManager) checkRoundId(lastRoundId int64) int64 {
	ymd := dateutil.YmdInt(dateutil.UTC())
	var sb strings.Builder
	if lastRoundId == 0 {
		sb.WriteString(strconv.FormatInt(ymd, 10))
		sb.WriteString(stringutil.PadLeft("1", "0", 4))
	} else {
		s := strconv.FormatInt(lastRoundId, 10)
		ymdOld, _ := stringutil.ToInt64(s[0:8], 0)
		if ymdOld == 0 {
			return lastRoundId + 1
		}
		if ymd > ymdOld {
			sb.WriteString(strconv.FormatInt(ymd, 10))
			sb.WriteString(stringutil.PadLeft("1", "0", 4))
		} else {
			return lastRoundId + 1
		}
	}
	newRoundId, _ := stringutil.ToInt64(sb.String(), 0)
	if newRoundId == 0 {
		newRoundId = lastRoundId + 1
	}
	return newRoundId
}

func (am *RouManager) NewRound(lastRoundId int64) error {
	defer logger.Recover()
	newRoundId := am.checkRoundId(lastRoundId)
	seed := am.genServerSeed() // 新轮次的服务端种子
	if seed == "" {
		logger.Error("[RouTower] gen seed fail", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", newRoundId))
		return fmt.Errorf("gen server seed fail")
	}
	t := dateutil.UnixSecs()
	am.runEntity = &roulettep.RouletteRoundEntity{
		Rid:         newRoundId,
		InstId:      am.inst.InstID,
		Start:       t,
		End:         0,
		Sseed:       seed,
		Sseed256:    secure.Sha256Hex(byteutil.StringToSliceByte(seed)),
		Clientseeds: []byte{},
		H512:        "",
		Status:      constant.RouUnActive,
		Count:       0,
		Created:     t,
		Updated:     t,
	}
	err := am.dao.CreateRound(am.runEntity)
	if err != nil {
		logger.Error("[RouTower] create round fail", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", newRoundId), zap.Error(err))
		return err
	}

	am.runRound = &roulettep.RouRound{
		RoundId: newRoundId,
		Start:   t,
		State:   constant.RouUnActive,
		Seed:    seed,
		Seed256: am.runEntity.Sseed256,
	}
	// 创建新轮次后 计算下一轮次种子
	return am.nextRoundInfo(newRoundId)
}

func (am *RouManager) nextRoundInfo(runRoundId int64) error {
	nextRoundId := am.checkRoundId(runRoundId)
	seed := am.genServerSeed()
	if seed == "" {
		logger.Error("[RouTower] gen next round seed fail", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", nextRoundId))
		return fmt.Errorf("gen next server seed fail")
	}
	am.runRound.Next = &roulettep.RouNextRound{
		RoundId: nextRoundId,
		Seed:    seed,
		H256:    secure.Sha256Hex(byteutil.StringToSliceByte(seed)),
	}
	return nil
}

func (am *RouManager) Destroy() {
	if am.betSub != nil {
		_ = am.betSub.Unsubscribe()
		am.betSub.Drain()
	}
	actorservice.Stop(am.ab.Pid())
	actorservice.Stop(am.pid)
}

// changeToState 新轮盘状态切换
func (am *RouManager) changeToState(state int32) {
	defer logger.Recover()
	switch state {
	case constant.RouBetState: // 运行投注阶段
		am.runEntity.Start = dateutil.UnixSecs()
		am.runEntity.Updated = am.runEntity.Start
		am.runEntity.Status = constant.RouBetState
		_, err := am.dao.ChangeToBetState(am.runEntity)
		if err != nil {
			logger.Error("[RouTower] change round to bet state fail", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid), zap.Error(err))
			return
		}
		am.runRound.State = constant.RouBetState
		am.runRound.Start = am.runEntity.Start
		st := &roulettep.RouState{
			RoundId:    am.runRound.RoundId,
			NewStateId: constant.RouBetState,
			StateStart: dateutil.UnixMilli(),
		}
		// 更新到ETCD，通知前置roubig状态变更
		if am.updateEtcdState(st) {
			am.cdRunner.Reset()
			am.ab.StopBetsInfoTimer()
			am.cdRunner.Run(am.runRound.RoundId)
			TimerAfter(time.Second, am.ab.BroadcastBetsInfo, nil)
		}
	case constant.RouStopBetState: // 下注截止
		am.runEntity.Updated = dateutil.UnixSecs()
		am.runEntity.Status = constant.RouStopBetState
		_, err := am.dao.ChangeToStopBetState(am.runEntity)
		if err != nil {
			logger.Error("[RouTower] change round to stop bet state fail", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid), zap.Error(err))
			return
		}
		am.runRound.State = constant.RouStopBetState
		st := &roulettep.RouState{
			RoundId:    am.runRound.RoundId,
			NewStateId: constant.RouStopBetState,
			StateStart: am.runEntity.Start * 1000,
		}
		// 更新到ETCD，通知前置roubig状态变更
		if am.updateEtcdState(st) {
			am.cdRunner.Run(am.runRound.RoundId)
		}
	case constant.RouOpenState: // 开奖
		st := &roulettep.RouState{
			RoundId:    am.runRound.RoundId,
			NewStateId: constant.RouOpenState,
			RoundEnd:   am.runEntity.End,
			RetNum:     am.runEntity.RetNum,
			RetColor:   am.runEntity.RetColor,
			RetGrp:     am.runEntity.RetGrp,
		}
		// 更新到ETCD，通知前置roubig状态变更
		if !am.updateEtcdState(st) {
			return
		}

		am.updateFlew()
		// 准备下一轮
		TimerAfter(time.Second, am.SettleNext, nil)
	}
}

func (am *RouManager) updateEtcdState(state *roulettep.RouState) bool {
	b, err := proto.Marshal(am.runRound)
	if err != nil {
		logger.Error("[RouTower] update round info to etcd fail on marshal run round", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid), zap.Error(err))
		return false
	}
	b1, er := proto.Marshal(state)
	if er != nil {
		logger.Error("[RouTower] update round info to etcd fail on marshal round state", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid), zap.Error(er))
		return false
	}
	// 同步更新轮次和状态
	ctx, cancel := context.WithTimeout(gloader.CClient().Context(), defaultTimeout)
	tresp, e1 := gloader.CClient().RawClient().Txn(ctx).
		Then(clientv3.OpPut(am.roundDir, byteutil.SliceByteToString(b)),
			clientv3.OpPut(am.stateDir, byteutil.SliceByteToString(b1))).
		Commit()
	cancel()

	if e1 != nil {
		logger.Error("[RouTower] update round info to etcd fail", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid), zap.Error(e1))
		return false
	}

	if !tresp.Succeeded {
		logger.Error("[RouTower] update round info to etcd fail in txn", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid))
		return false
	}

	return true
}

// Open 开奖
func (am *RouManager) Open() {
	defer logger.Recover()
	am.Flush()
	am.ab.StopBetsInfoTimer()
	am.runRound.State = constant.RouOpenState
	am.runEntity.End = dateutil.UnixSecs()
	am.runEntity.Updated = am.runEntity.End
	am.runEntity.Status = constant.RouOpenState
	am.runEntity.Count = am.ab.RoundBetCount()
	first3, combineStr := am.ab.First3Bets()
	b, err1 := json.Marshal(first3)
	if err1 != nil {
		logger.Error("[RouTower] marshal first3 bets fail", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid), zap.Error(err1))
		b = []byte{}
	}
	am.runEntity.Clientseeds = b
	// 计算结果
	h512, retNum, retColor, retGrp := am.rouHelp.Draw(am.runEntity.Sseed, combineStr, am.cfg.GameConfig)
	am.runEntity.H512 = h512
	am.runEntity.RetNum = retNum
	am.runEntity.RetColor = retColor
	am.runEntity.RetGrp = retGrp

	_, err := am.dao.ChangeToOpenState(am.runEntity)
	if err != nil {
		logger.Error("[RouTower] change round state to run fail", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid), zap.Error(err))
		return
	}
	am.runRound.First3 = first3
	am.runRound.H512 = h512
	am.runRound.End = am.runEntity.End
	am.runRound.RetNum = retNum
	am.runRound.RetColor = retColor
	am.runRound.RetGrp = retGrp
	am.changeToState(constant.RouOpenState)
}

func (am *RouManager) updateFlew() {
	flew := &roulettep.RouRound{
		RoundId:  am.runRound.RoundId,
		Start:    am.runRound.Start,
		End:      am.runRound.End,
		Seed:     am.runRound.Seed,
		First3:   am.runRound.First3,
		H512:     am.runRound.H512,
		RetNum:   am.runRound.RetNum,
		RetColor: am.runRound.RetColor,
		RetGrp:   am.runRound.RetGrp,
		Seed256:  am.runRound.Seed256,
	}
	actorservice.Send(am.pid, flew)
}

func (am *RouManager) SettleNext(data interface{}) {
	if am.status == mcomm.RunState_RsOnStop {
		return
	}
	am.ab.PrepareForNextRound()
	actorservice.Send(am.pid, NewRound{RoundId: am.runRound.RoundId})
}

func (am *RouManager) NextRound(data interface{}) {
	if am.status == mcomm.RunState_RsOnStop {
		return
	}
	actorservice.Send(am.pid, NewRound{RoundId: am.runRound.RoundId})
}

// PublishCurrentElapse 倒计时播报
func (am *RouManager) PublishCurrentElapse(cd int64) {
	st := response.AcquireElapse()
	defer response.ReleaseElapse(st)
	st.Remain = cd
	st.State = am.runRound.State
	b, err := proto.Marshal(st)
	if err != nil {
		logger.Error("[RouTower] publish round current elapse fail on marshal state", zap.Int64("InstID", am.inst.InstID), zap.Int64("RoundId", am.runEntity.Rid), zap.Error(err))
		return
	}
	err = gloader.CommMgr().NatsC().Publish(am.keyCountDown, b)
	if err != nil {
	}
}

func (am *RouManager) Flush() {
	_ = gloader.CommMgr().NatsC().Flush()
}
